package com.planw.beetl.service;

import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.impl.file.PsiFileImplUtil;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.PsiFileSystemItemUtil;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.ui.JBColor;
import com.intellij.util.PlatformIcons;
import com.planw.beetl.utils.PsiKt;
import com.planw.beetl.utils.StrUtil;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.model.MavenArtifact;
import org.jetbrains.idea.maven.project.MavenProject;
import org.jetbrains.idea.maven.project.MavenProjectsManager;

public class BeetlSqlService {

  private static final Logger logger = Logger.getInstance(BeetlSqlService.class);

  private static Project project;

  private static JavaPsiFacade javaPsiFacade;

  private final PsiClass baseMapperPsiClass;

  private final GlobalSearchScope allSearchScope;

  private final GlobalSearchScope javaSearchScope;

  public static String BEETL_SQL_PATH = "sql/";

  public static String BASE_MAPPER_CLASS = "org.beetl.sql.core.mapper.BaseMapper";

  public static String SQL_RESOURCE_CLASS = "org.beetl.sql.core.annotatoin.SqlResource";

  public static String PARAM_CLASS = "org.beetl.sql.core.annotatoin.Param";

  public static String PAGE_QUERY_CLASS = "org.beetl.sql.core.engine.PageQuery";

  public static String PAGE_REQUEST_CLASS = "org.beetl.sql.core.page.PageRequest";

  public static String BEETL_MAVEN = "beetlsql";

  public static final Map<String, LookupElement> LOOKUP_ELEMENTS_CACHE = new HashMap<>();

  private boolean isDependOnBeetSql = false;

  public static final Map<PsiFile, PsiClass> mapperCache = new ConcurrentHashMap<>();

  public BeetlSqlService(Project project) {

    BeetlSqlService.project = project;
    BeetlSqlService.javaPsiFacade = JavaPsiFacade.getInstance(project);
    init(project);
    this.javaSearchScope = GlobalSearchScope
        .getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), JavaFileType.INSTANCE);
    this.allSearchScope = GlobalSearchScope.allScope(project);
//    this.baseMapperPsiClass = JavaFileManager.getInstance(project)
//        .findClass(BASE_MAPPER_CLASS, allSearchScope);

    this.baseMapperPsiClass = javaPsiFacade.findClass(BASE_MAPPER_CLASS, allSearchScope);

    logger.info(String.format("基本环境" + baseMapperPsiClass));
  }

  public void init(Project project) {

    MavenProjectsManager mavenProjectsManager = MavenProjectsManager.getInstance(project);
    List<MavenProject> mavenProjectList = mavenProjectsManager.getProjects();
    for (MavenProject mavenProject : mavenProjectList) {
      List<MavenArtifact> mavenArtifacts = mavenProject.findDependencies("com.ibeetl", "beetlsql");
      if (CollectionUtils.isNotEmpty(mavenArtifacts)) {
        MavenArtifact first = mavenArtifacts.get(0);
        if (first.getVersion().startsWith("2")) {
          initBeetlSQL2();
        } else {
          initBeetlSQL3();
        }

        logger.info(String.format("找到beetl依赖 " + first.getVersion()));

        break;
      }
    }
  }

  public void initBeetlSQL2() {
    //donothing
  }

  public void initBeetlSQL3() {

    BEETL_MAVEN = "beetlsql";
    BASE_MAPPER_CLASS = "org.beetl.sql.mapper.BaseMapper";
    SQL_RESOURCE_CLASS = "org.beetl.sql.mapper.annotation.SqlResource";
    PARAM_CLASS = "org.beetl.sql.mapper.annotation.Param";
    PAGE_QUERY_CLASS = "org.beetl.sql.core.page.PageReqeust";


  }

  public boolean isDependOnBeetlSql() {

    MavenProjectsManager mavenProjectsManager = MavenProjectsManager.getInstance(project);
    List<MavenProject> mavenProjectList = mavenProjectsManager.getProjects();
    logger.info("【beetlsql】maven project size " + CollectionUtils.size(mavenProjectList));
    logger.info("【beetlsql】constant  BEETL_MAVEN is " + BEETL_MAVEN);
    for (MavenProject mavenProject : mavenProjectList) {
      List<MavenArtifact> mavenArtifacts = mavenProject.findDependencies("com.ibeetl", BEETL_MAVEN);
      logger.info("【beetlsql】mavenArtifacts size " + CollectionUtils.size(mavenArtifacts));
      isDependOnBeetSql = CollectionUtils.isNotEmpty(mavenArtifacts);
      if (isDependOnBeetSql) {
        break;
      }
    }
    return isDependOnBeetSql;
  }

  public static BeetlSqlService getInstance(Project project) {

    return (BeetlSqlService) project.getPicoContainer()
        .getComponentInstanceOfType(BeetlSqlService.class);
  }

  public PsiAnnotation getParamAnnotation(PsiParameter parameter) {

    PsiAnnotation annotation = parameter.getAnnotation(BeetlSqlService.PARAM_CLASS);
    return annotation;
  }

  public String getParamAnnotationValue(PsiParameter parameter) {

    PsiAnnotation annotation = this.getParamAnnotation(parameter);
    if (annotation == null) {
      return null;
    }
    return Optional.ofNullable(annotation.findDeclaredAttributeValue("value"))
        .map(PsiElement::getText).orElse("")
        .replaceAll("\"", "");
  }

  public PsiAnnotation getRootAnnotation(PsiParameter parameter) {

    PsiAnnotation annotation = parameter.getAnnotation("org.beetl.sql.mapper.annotation.Root");
    return annotation;
  }

  public boolean isPageQuery(PsiParameter parameter) {

    String className = PsiKt.getClassName(parameter.getType());
    return StringUtils.equals(BeetlSqlService.PAGE_QUERY_CLASS, className);
  }

  public boolean isPageRequest(PsiParameter parameter) {

    String className = PsiKt.getClassName(parameter.getType());
    return StringUtils.equals(BeetlSqlService.PAGE_REQUEST_CLASS, className);
  }

  /**
   * 通过函数名搜索默认函数
   *
   * @param lookupStr
   *     函数名
   */
  public List<LookupElement> findBeetlSqlFunction(String lookupStr) {

    this.loadBeetlSqlDefaultFunction();
    Set<Entry<String, LookupElement>> entrySet = LOOKUP_ELEMENTS_CACHE.entrySet();
    return entrySet.stream().filter(entry -> StrUtil.searchCharSequence(entry.getKey(), lookupStr))
        .map(Entry::getValue).collect(
            Collectors.toList());
  }

  /**
   * 载入btsql.properties中的函数
   */
  private void loadBeetlSqlDefaultFunction() {

    if (!LOOKUP_ELEMENTS_CACHE.isEmpty()) {
      return;
    }
    Collection<VirtualFile> virtualFiles = FilenameIndex
        .getVirtualFilesByName(project, "btsql.properties", allSearchScope);
    Pattern BEETL_SQL_FN = Pattern.compile("(FN|TAG)[\\w\\.&&[^_]]+");
    Pattern word = Pattern.compile("[\\w]+");
    try {
      for (VirtualFile virtualFile : virtualFiles) {
        Properties properties = new Properties();
        InputStream inputStream = virtualFile.getInputStream();
        properties.load(inputStream);
        Set<Object> keySet = properties.keySet();
        for (Object key : keySet) {
          String keyStr = String.valueOf(key);
          Matcher fnMatcher = BEETL_SQL_FN.matcher(keyStr);
          if (fnMatcher.matches()) {
            Matcher wordMatcher = word.matcher(keyStr);
            String fnName = null;
            while (wordMatcher.find()) {
              fnName = wordMatcher.group();
            }
            if (StringUtils.isNotBlank(fnName)) {
              LookupElement lookupElement = this
                  .createFunctionLookupElement(fnName, properties.getProperty(keyStr));
              LOOKUP_ELEMENTS_CACHE.put(fnName, lookupElement);
            }
          }
        }
      }
    } catch (IOException e) {
      logger.error("无法获取beetlsql的btsql.properties文件");
    }
  }

  private LookupElement createFunctionLookupElement(String lookupStr, String typeText) {

    LookupElement lookupElement = LookupElementBuilder
        .create(lookupStr + "()")
        .withCaseSensitivity(false)
        .withPresentableText(lookupStr)
        .withIcon(PlatformIcons.FUNCTION_ICON)
        .withItemTextForeground(JBColor.BLACK)
        .bold()
        .withTypeText(typeText, PlatformIcons.CLASS_ICON, true)
        .withTypeIconRightAligned(true)
        .withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE);

    return lookupElement;
  }

  /**
   * 获取所有继承了BaseMapper的接口
   */
  public Collection<PsiClass> getAllMappers() {

    return ClassInheritorsSearch
        .search(baseMapperPsiClass, this.javaSearchScope, true).findAll();
  }

  /**
   * 通过markdown文件查找对应的BaseMapper。<br/> 必须符合：优先注解SqlResource，然后是泛型指定的实体类名
   *
   * @param mdPsiFile
   *     markdown的PsiFile
   */
  public PsiClass getMapperByMdFile(PsiFile mdPsiFile) {

    String markdownFileName = mdPsiFile.getName();
    String mdExtension = mdPsiFile.getFileType().getDefaultExtension();
    VirtualFile virtualFile = mdPsiFile.getOriginalFile().getVirtualFile();
    String path = virtualFile.getPath();
    logger.info("【beetlsql】 path " + path);
    String inBeetlsqlDir = path
        .substring(path.indexOf(BEETL_SQL_PATH) + BEETL_SQL_PATH.length(),
            path.indexOf(mdExtension) - 1);
    logger.info("【beetlsql】 inBeetlsqlDir " + inBeetlsqlDir);
//    去除.md后缀
    String fileNameNoExt = markdownFileName.substring(0, markdownFileName.indexOf(mdExtension)
        - 1);
    logger.info("【beetlsql】 fileNameNoExt " + fileNameNoExt);
    //    将路径类似：core/user 转成core.user ，用于匹配注解SqlResource的值
    String sqlResourceValue = String.format("\"%s\"", inBeetlsqlDir.replaceAll("/", "."));
    logger.info("【beetlsql】 sqlResourceValue " + sqlResourceValue);

    PsiClass mapper = null;
    Collection<PsiClass> allMappers = this.getAllMappers();

    boolean flag = false;
    for (PsiClass mapperPsiCls : allMappers) {
      PsiAnnotation sqlResource = mapperPsiCls.getAnnotation(SQL_RESOURCE_CLASS);
//      优先匹配注解
      if (sqlResource != null) {

        PsiAnnotationMemberValue value = sqlResource.findDeclaredAttributeValue("value");
        logger.info("【beetlsql】" + value.getText() +
            "---sql resource diff result :" + StringUtils
            .equals(value.getText(), sqlResourceValue));
        if (StringUtils.equals(value.getText(), sqlResourceValue)) {
          mapper = mapperPsiCls;
          flag = true;
          break;
        }
      } else {
//        通过继承的BaseMapper接口的泛型类判断
        @NotNull PsiJavaCodeReferenceElement[] extendMapperInterFaces = mapperPsiCls
            .getExtendsList()
            .getReferenceElements();
        for (PsiJavaCodeReferenceElement baseMapperInterface : extendMapperInterFaces) {
          @NotNull PsiTypeElement[] typeParameterElements = baseMapperInterface.getParameterList()
              .getTypeParameterElements();
          for (PsiTypeElement typeParameterElement : typeParameterElements) {
            logger.info("【beetlsql】" + typeParameterElement.getText() +
                "---type parameter diff result :" + StringUtils
                .equals(typeParameterElement.getText(), StringUtils.capitalize(fileNameNoExt)));
            if (StringUtils
                .equals(typeParameterElement.getText(), StringUtils.capitalize(fileNameNoExt))) {
              mapper = mapperPsiCls;
              flag = true;
              break;
            }
          }
          if (flag) {
            break;
          }
        }
      }
    }

    return mapper;
  }

}
